///////////////////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
// Copyright (C) 2001-2025 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
///////////////////////////////////////////////////////////////////////////////////////////////

package com.puppycrawl.tools.checkstyle.checks.javadoc;

import static com.google.common.truth.Truth.assertWithMessage;

import org.junit.jupiter.api.Test;

import com.puppycrawl.tools.checkstyle.DetailAstImpl;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;

public class JavadocTagInfoTest {

    /* Additional test for jacoco, since valueOf()
     * is generated by javac and jacoco reports that
     * valueOf() is uncovered.
     */
    @Test
    public void testJavadocTagInfoValueOf() {
        final JavadocTagInfo tag = JavadocTagInfo.valueOf("AUTHOR");
        assertWithMessage("Invalid valueOf result")
            .that(tag)
            .isEqualTo(JavadocTagInfo.AUTHOR);
    }

    /* Additional test for jacoco, since valueOf()
     * is generated by javac and jacoco reports that
     * valueOf() is uncovered.
     */
    @Test
    public void testTypeValueOf() {
        final JavadocTagInfo.Type type = JavadocTagInfo.Type.valueOf("BLOCK");
        assertWithMessage("Invalid valueOf result")
            .that(type)
            .isEqualTo(JavadocTagInfo.Type.BLOCK);
    }

    /* Additional test for jacoco, since values()
     * is generated by javac and jacoco reports that
     * values() is uncovered.
     */
    @Test
    public void testTypeValues() {
        final JavadocTagInfo.Type[] expected = {
            JavadocTagInfo.Type.BLOCK,
            JavadocTagInfo.Type.INLINE,
        };
        final JavadocTagInfo.Type[] actual = JavadocTagInfo.Type.values();
        assertWithMessage("Invalid Type values")
            .that(actual)
            .isEqualTo(expected);
    }

    @Test
    public void testAuthor() {
        final DetailAstImpl ast = new DetailAstImpl();

        final int[] validTypes = {
            TokenTypes.PACKAGE_DEF,
            TokenTypes.CLASS_DEF,
            TokenTypes.INTERFACE_DEF,
            TokenTypes.ENUM_DEF,
            TokenTypes.ANNOTATION_DEF,
        };
        for (int type: validTypes) {
            ast.setType(type);
            assertWithMessage("Invalid ast type for current tag: " + ast.getType())
                    .that(JavadocTagInfo.AUTHOR.isValidOn(ast))
                    .isTrue();
        }

        ast.setType(TokenTypes.LAMBDA);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.AUTHOR.isValidOn(ast))
                .isFalse();
    }

    @Test
    public void testOthers() throws ReflectiveOperationException {
        final JavadocTagInfo[] tags = {
            JavadocTagInfo.CODE,
            JavadocTagInfo.DOC_ROOT,
            JavadocTagInfo.LINK,
            JavadocTagInfo.LINKPLAIN,
            JavadocTagInfo.LITERAL,
            JavadocTagInfo.SEE,
            JavadocTagInfo.SINCE,
            JavadocTagInfo.VALUE,
        };
        for (JavadocTagInfo tagInfo : tags) {
            final DetailAstImpl astParent = new DetailAstImpl();
            astParent.setType(TokenTypes.LITERAL_CATCH);

            final DetailAstImpl ast = new DetailAstImpl();
            TestUtil.invokeVoidMethod(ast, "setParent", astParent);

            final int[] validTypes = {
                TokenTypes.PACKAGE_DEF,
                TokenTypes.CLASS_DEF,
                TokenTypes.INTERFACE_DEF,
                TokenTypes.ENUM_DEF,
                TokenTypes.ANNOTATION_DEF,
                TokenTypes.METHOD_DEF,
                TokenTypes.CTOR_DEF,
                TokenTypes.VARIABLE_DEF,
            };
            for (int type: validTypes) {
                ast.setType(type);
                assertWithMessage("Invalid ast type for current tag: " + ast.getType())
                        .that(tagInfo.isValidOn(ast))
                        .isTrue();
            }

            astParent.setType(TokenTypes.SLIST);
            ast.setType(TokenTypes.VARIABLE_DEF);
            assertWithMessage("Should return false when ast type is invalid for current tag")
                    .that(tagInfo.isValidOn(ast))
                    .isFalse();

            ast.setType(TokenTypes.PARAMETER_DEF);
            assertWithMessage("Should return false when ast type is invalid for current tag")
                    .that(tagInfo.isValidOn(ast))
                    .isFalse();
        }
    }

    @Test
    public void testDeprecated() throws ReflectiveOperationException {
        final DetailAstImpl ast = new DetailAstImpl();
        final DetailAstImpl astParent = new DetailAstImpl();
        astParent.setType(TokenTypes.LITERAL_CATCH);
        TestUtil.invokeVoidMethod(ast, "setParent", astParent);

        final int[] validTypes = {
            TokenTypes.CLASS_DEF,
            TokenTypes.INTERFACE_DEF,
            TokenTypes.ENUM_DEF,
            TokenTypes.ANNOTATION_DEF,
            TokenTypes.METHOD_DEF,
            TokenTypes.CTOR_DEF,
            TokenTypes.ENUM_CONSTANT_DEF,
            TokenTypes.ANNOTATION_FIELD_DEF,
            TokenTypes.VARIABLE_DEF,
        };
        for (int type: validTypes) {
            ast.setType(type);
            assertWithMessage("Invalid ast type for current tag: " + ast.getType())
                    .that(JavadocTagInfo.DEPRECATED.isValidOn(ast))
                    .isTrue();
        }

        astParent.setType(TokenTypes.SLIST);
        ast.setType(TokenTypes.VARIABLE_DEF);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.DEPRECATED.isValidOn(ast))
                .isFalse();

        ast.setType(TokenTypes.PARAMETER_DEF);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.DEPRECATED.isValidOn(ast))
                .isFalse();
    }

    @Test
    public void testSerial() throws ReflectiveOperationException {
        final DetailAstImpl ast = new DetailAstImpl();
        final DetailAstImpl astParent = new DetailAstImpl();
        astParent.setType(TokenTypes.LITERAL_CATCH);
        TestUtil.invokeVoidMethod(ast, "setParent", astParent);

        final int[] validTypes = {
            TokenTypes.VARIABLE_DEF,
        };
        for (int type: validTypes) {
            ast.setType(type);
            assertWithMessage("Invalid ast type for current tag: " + ast.getType())
                    .that(JavadocTagInfo.SERIAL.isValidOn(ast))
                    .isTrue();
        }

        astParent.setType(TokenTypes.SLIST);
        ast.setType(TokenTypes.VARIABLE_DEF);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.SERIAL.isValidOn(ast))
                .isFalse();

        ast.setType(TokenTypes.PARAMETER_DEF);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.SERIAL.isValidOn(ast))
                .isFalse();
    }

    @Test
    public void testException() {
        final DetailAstImpl ast = new DetailAstImpl();

        final int[] validTypes = {
            TokenTypes.METHOD_DEF,
            TokenTypes.CTOR_DEF,
        };
        for (int type: validTypes) {
            ast.setType(type);
            assertWithMessage("Invalid ast type for current tag: " + ast.getType())
                    .that(JavadocTagInfo.EXCEPTION.isValidOn(ast))
                    .isTrue();
        }

        ast.setType(TokenTypes.LAMBDA);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.EXCEPTION.isValidOn(ast))
                .isFalse();
    }

    @Test
    public void testThrows() {
        final DetailAstImpl ast = new DetailAstImpl();

        final int[] validTypes = {
            TokenTypes.METHOD_DEF,
            TokenTypes.CTOR_DEF,
        };
        for (int type: validTypes) {
            ast.setType(type);
            assertWithMessage("Invalid ast type for current tag: " + ast.getType())
                    .that(JavadocTagInfo.THROWS.isValidOn(ast))
                    .isTrue();
        }

        ast.setType(TokenTypes.LAMBDA);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.THROWS.isValidOn(ast))
                .isFalse();
    }

    @Test
    public void testVersions() {
        final DetailAstImpl ast = new DetailAstImpl();

        final int[] validTypes = {
            TokenTypes.PACKAGE_DEF,
            TokenTypes.CLASS_DEF,
            TokenTypes.INTERFACE_DEF,
            TokenTypes.ENUM_DEF,
            TokenTypes.ANNOTATION_DEF,
        };
        for (int type: validTypes) {
            ast.setType(type);
            assertWithMessage("Invalid ast type for current tag: " + ast.getType())
                    .that(JavadocTagInfo.VERSION.isValidOn(ast))
                    .isTrue();
        }

        ast.setType(TokenTypes.LAMBDA);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.VERSION.isValidOn(ast))
                .isFalse();
    }

    @Test
    public void testParam() {
        final DetailAstImpl ast = new DetailAstImpl();

        final int[] validTypes = {
            TokenTypes.CLASS_DEF,
            TokenTypes.INTERFACE_DEF,
            TokenTypes.METHOD_DEF,
            TokenTypes.CTOR_DEF,
        };
        for (int type: validTypes) {
            ast.setType(type);
            assertWithMessage("Invalid ast type for current tag: " + ast.getType())
                    .that(JavadocTagInfo.PARAM.isValidOn(ast))
                    .isTrue();
        }

        ast.setType(TokenTypes.LAMBDA);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.PARAM.isValidOn(ast))
                .isFalse();
    }

    @Test
    public void testReturn() {
        final DetailAstImpl ast = new DetailAstImpl();
        final DetailAstImpl astChild = new DetailAstImpl();
        astChild.setType(TokenTypes.TYPE);
        ast.setFirstChild(astChild);
        final DetailAstImpl astChild2 = new DetailAstImpl();
        astChild2.setType(TokenTypes.LITERAL_INT);
        astChild.setFirstChild(astChild2);

        final int[] validTypes = {
            TokenTypes.METHOD_DEF,
        };
        for (int type: validTypes) {
            ast.setType(type);
            assertWithMessage("Invalid ast type for current tag: " + ast.getType())
                    .that(JavadocTagInfo.RETURN.isValidOn(ast))
                    .isTrue();
        }

        astChild2.setType(TokenTypes.LITERAL_VOID);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.RETURN.isValidOn(ast))
                .isFalse();

        ast.setType(TokenTypes.LAMBDA);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.RETURN.isValidOn(ast))
                .isFalse();

        astChild2.setType(TokenTypes.LITERAL_INT);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.RETURN.isValidOn(ast))
                .isFalse();
    }

    @Test
    public void testSerialField() {
        final DetailAstImpl ast = new DetailAstImpl();
        final DetailAstImpl astChild = new DetailAstImpl();
        astChild.setType(TokenTypes.TYPE);
        ast.setFirstChild(astChild);
        final DetailAstImpl astChild2 = new DetailAstImpl();
        astChild2.setType(TokenTypes.ARRAY_DECLARATOR);
        astChild2.setText("ObjectStreamField");
        astChild.setFirstChild(astChild2);

        final int[] validTypes = {
            TokenTypes.VARIABLE_DEF,
        };
        for (int type: validTypes) {
            ast.setType(type);
            assertWithMessage("Invalid ast type for current tag: " + ast.getType())
                    .that(JavadocTagInfo.SERIAL_FIELD.isValidOn(ast))
                    .isTrue();
        }

        final int[] invalidTypes = {
            TokenTypes.METHOD_DEF,
            TokenTypes.CLASS_DEF,
            TokenTypes.INTERFACE_DEF,
            TokenTypes.ENUM_DEF,
        };
        for (int type: invalidTypes) {
            ast.setType(type);
            assertWithMessage("@serialField should only be valid on VARIABLE_DEF, not on others")
                .that(JavadocTagInfo.SERIAL_FIELD.isValidOn(ast))
                .isFalse();
        }

        astChild2.setText("1111");
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.SERIAL_FIELD.isValidOn(ast))
                .isFalse();

        astChild2.setType(TokenTypes.LITERAL_VOID);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.SERIAL_FIELD.isValidOn(ast))
                .isFalse();

        ast.setType(TokenTypes.LAMBDA);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.SERIAL_FIELD.isValidOn(ast))
                .isFalse();

        ast.setType(TokenTypes.VARIABLE_DEF);
        astChild2.setType(TokenTypes.IDENT);
        astChild2.setText("ObjectStreamField");
        assertWithMessage("Should return false when type is not ARRAY_DECLARATOR ")
                .that(JavadocTagInfo.SERIAL_FIELD.isValidOn(ast))
                .isFalse();

        ast.setType(TokenTypes.VARIABLE_DEF);
        astChild2.setType(TokenTypes.ARRAY_DECLARATOR);
        astChild2.setText("WrongField");
        assertWithMessage("Should return false when text is not ObjectStreamField")
                .that(JavadocTagInfo.SERIAL_FIELD.isValidOn(ast))
                .isFalse();
    }

    @Test
    public void testSerialData() {
        final DetailAstImpl ast = new DetailAstImpl();
        ast.setType(TokenTypes.METHOD_DEF);
        final DetailAstImpl astChild = new DetailAstImpl();
        astChild.setType(TokenTypes.IDENT);
        astChild.setText("writeObject");
        ast.setFirstChild(astChild);

        final String[] validNames = {
            "writeObject",
            "readObject",
            "writeExternal",
            "readExternal",
            "writeReplace",
            "readResolve",
        };
        for (String name: validNames) {
            astChild.setText(name);
            assertWithMessage("Invalid ast type for current tag: " + ast.getType())
                    .that(JavadocTagInfo.SERIAL_DATA.isValidOn(ast))
                    .isTrue();
        }

        astChild.setText("1111");
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.SERIAL_DATA.isValidOn(ast))
                .isFalse();

        ast.setType(TokenTypes.LAMBDA);
        assertWithMessage("Should return false when ast type is invalid for current tag")
                .that(JavadocTagInfo.SERIAL_DATA.isValidOn(ast))
                .isFalse();

        astChild.setText("writeObject");
        assertWithMessage("Should return false when ast type is invalid for current tag")
                 .that(JavadocTagInfo.SERIAL_DATA.isValidOn(ast))
                 .isFalse();
    }

    @Test
    public void testCoverage() {
        assertWithMessage("Invalid type")
            .that(JavadocTagInfo.VERSION.getType())
            .isEqualTo(JavadocTagInfo.Type.BLOCK);

        assertWithMessage("Invalid toString result")
            .that(JavadocTagInfo.VERSION.toString())
            .isEqualTo("text [@version] name [version] type [BLOCK]");

        try {
            JavadocTagInfo.fromName(null);
            assertWithMessage("IllegalArgumentException is expected").fail();
        }
        catch (IllegalArgumentException exc) {
            assertWithMessage("Invalid exception message")
                .that(exc.getMessage())
                .isEqualTo("the name is null");
        }

        try {
            JavadocTagInfo.fromName("myname");
            assertWithMessage("IllegalArgumentException is expected").fail();
        }
        catch (IllegalArgumentException exc) {
            assertWithMessage("Invalid exception message")
                .that(exc.getMessage())
                .isEqualTo("the name [myname] is not a valid Javadoc tag name");
        }

        try {
            JavadocTagInfo.fromText(null);
            assertWithMessage("IllegalArgumentException is expected").fail();
        }
        catch (IllegalArgumentException exc) {
            assertWithMessage("Invalid exception message")
                .that(exc.getMessage())
                .isEqualTo("the text is null");
        }

        try {
            JavadocTagInfo.fromText("myname");
            assertWithMessage("IllegalArgumentException is expected").fail();
        }
        catch (IllegalArgumentException exc) {
            assertWithMessage("Invalid exception message")
                .that(exc.getMessage())
                .isEqualTo("the text [myname] is not a valid Javadoc tag text");
        }

        assertWithMessage("Invalid fromText result")
            .that(JavadocTagInfo.fromText("@version"))
            .isEqualTo(JavadocTagInfo.VERSION);
    }

}
